package cn.source.system.task;

import cn.source.common.utils.DateUtils;
import cn.source.common.utils.file.FileUtils;
import cn.source.system.domain.*;
import cn.source.system.service.IDbConnService;
import cn.source.system.service.IDbRecordService;
import cn.source.system.service.IDbTaskService;
import cn.source.system.utils.ConnUtils;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static cn.source.system.service.impl.DbConnServiceImpl.testException;

@Component("dbbackupTask")
@Slf4j
public class DbBackupTask {

    @Value("${backup.path}")
    private String backupPath;//备份路径

    @Value("${backup.mysqlbinpath}")
    private String mysqlbinpath;//mysql安装路径

    @Autowired
    private IDbRecordService dbRecordService;

    @Autowired
    private IDbConnService dbConnService;

    @Value("${backup.csv.fieldsTerminatedBy}")
    private String fieldsTerminatedBy;
    @Value("${backup.csv.linesTerminatedBy}")
    private String linesTerminatedBy;
    @Value("${backup.csv.enclosedBy}")
    private String enclosedBy;


    @Autowired
    private IDbTaskService dbTaskService;

    public void backup(Integer taskId) {
        DbTask taskParam = dbTaskService.selectDbTaskById(taskId.longValue());
        String param = JSON.toJSONString(taskParam);
        log.info("开始备份数据库 参数" + param);
        //开始时间
        long startTime = System.currentTimeMillis();
        String format = taskParam.getFormat();
        DbRecord dbRecord = null;
        if (format.equals("csv")) {
            dbRecord = backupForCsv(taskParam);
        } else if (format.equals("sql")) {
            dbRecord = backupForSql(taskParam);
        }
        //结束时间
        long endTime = System.currentTimeMillis();
        log.info("备份数据库结束 参数" + param + " 耗时" + (endTime - startTime) + "ms");

        //保存备份记录
        dbRecordService.insertDbRecord(dbRecord);

        //后置处理
        if (dbRecord.getStatus().equals("success")) {
            postProc(taskParam, dbRecord);
        }
    }

    /**
     * 将中文转换为拼音
     *
     * @param chinese 中文字符串
     * @return 拼音字符串
     */
    private String convertToPinyin(String chinese) {
        StringBuilder pinyin = new StringBuilder();
        char[] chars = chinese.toCharArray();
        for (char c : chars) {
            String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
            if (pinyinArray != null && pinyinArray.length > 0) {
                pinyin.append(pinyinArray[0].replaceAll("\\d", ""));
            } else {
                pinyin.append(c);
            }
        }
        return pinyin.toString();
    }

    /**
     * csl备份，
     *
     * @param task
     * @return
     */
    private DbRecord backupForCsv(DbTask task) {

        String name = task.getName();//任务名称
        String rootPath = backupPath + File.separator + name;

        Date date = new Date();
        String s = DateUtils.dateTimeSecond(date);
        //创建备份目录
        File file2 = new File(rootPath + File.separator + s);
        file2.mkdirs();

        String connName = task.getConnName();
        String[] tables = task.getTables().split("#");
        DbRecord dbRecord = new DbRecord();

        //查询连接信息
        DbConn conn = dbConnService.findOneByConnName(connName);
        ConnInfo info = ConnUtils.urlParse(conn.getConnUrl());
        String dbName = info.getDbName();

        List<TableToFile> tableToFiles = new ArrayList<>();

        for (String table : tables) {

            TableToFile tableToFile = new TableToFile();

            StringBuilder buffer = new StringBuilder();

            // 将中文转换为拼音
            String pinyinTable = convertToPinyin(table);

            File file = new File(file2, pinyinTable + ".csv");
            String filePath = file.getAbsolutePath();
            // 判断是否为 Windows 系统
            if (File.separatorChar == '\\') {
                filePath = filePath.replace("\\", "\\\\");
            }

            buffer.append("select * from ").append(table).append(" into outfile '")
                    .append(filePath).append("'")
                    .append(" FIELDS TERMINATED BY '").append(fieldsTerminatedBy)
                    .append("' ENCLOSED BY '").append(enclosedBy)
                    .append("' LINES TERMINATED BY '").append(linesTerminatedBy).append("'");
            try {
                log.info(buffer.toString());
                ConnUtils.execOutFile(connName, buffer.toString());

                tableToFile.setFileName(pinyinTable + ".csv");
                tableToFile.setTableName(table);
                tableToFile.setFileSize(file.length());
                tableToFile.setFileAbsolutePath(file.getAbsolutePath());
                tableToFile.setDbName(dbName);

                tableToFiles.add(tableToFile);

            } catch (Exception e) {
                dbRecord.setStatus("fail");
                dbRecord.setErrMsg(e.getMessage());
            }
        }
        dbRecord.setConnName(task.getConnName());
        if (dbRecord.getStatus() == null) {
            dbRecord.setStatus("success");
        }
        dbRecord.setTaskName(task.getName());
        dbRecord.setTablesToFile(JSON.toJSONString(tableToFiles));
        dbRecord.setStartTime(DateUtils.dateTime(s));
        dbRecord.setEndTime(new Date());

        return dbRecord;
    }

    private void postProc(DbTask task, DbRecord dbRecord) {
        //清空最大保留的备份数量
        String name = task.getName();//任务名称
        String rootPath = backupPath + File.separator + name;
        Integer maxNum = task.getMaxNum();//最大备份数量
        if (maxNum == null) {
            maxNum = 1;
        }
        //查询备份记录，按照最大备份数量删除多余的备份记录
        List<DbRecord> dbRecords = dbRecordService.selectDbRecordForTimeLimit(maxNum, task.getName());
        Set<String> foldName = dbRecords.stream().map(item -> DateUtils.dateTimeSecond(item.getStartTime())).collect(Collectors.toSet());
        //删除多余的备份记录
        File file = new File(rootPath);
        File[] files = file.listFiles();
        if (files == null) {
            return;
        }
        for (File file1 : files) {
            if (foldName.contains(file1.getName())) {
                //删除文件夹
                FileUtils.deleteFolder(file1);
            }
        }
        //转存数据
        String transToConnList = task.getTransToConnList();
        if (transToConnList != null && !transToConnList.isEmpty()) {
            String[] split = transToConnList.split("#");
            for (String s : split) {
                String tablesToFile = dbRecord.getTablesToFile();
                try {
                    dbConnService.createRecoveryTask(s, tablesToFile,task);
                    //保证每次备份相互隔离不影响
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 通过mysqldump备份
     *
     * @param task
     */
    private DbRecord backupForSql(DbTask task) {

        String name = task.getName();//任务名称
        String rootPath = backupPath + File.separator + name;

        //开始备份
        String dumpBin = mysqlbinpath + File.separator + "mysqldump";
        //查询连接信息
        DbConn conn = dbConnService.findOneByConnName(task.getConnName());
        ConnInfo info = ConnUtils.urlParse(conn.getConnUrl());

        Date date = new Date();
        String s = DateUtils.dateTimeSecond(date);
        //创建备份目录
        File file2 = new File(rootPath + File.separator + s);
        file2.mkdirs();

        String[] tables = task.getTables().split("#");

        DbRecord dbRecord = new DbRecord();
        List<TableToFile> tableToFiles = new ArrayList<>();

        //错误消息
        StringBuilder errMsg = new StringBuilder();

        for (String table : tables) {
            StringBuffer buffer = new StringBuffer();

            TableToFile tableToFile = new TableToFile();

            buffer.append(dumpBin)
                    .append(" -u ")
                    .append(conn.getDbUser()).append(" ")
                    .append("-p").append(conn.getDbPass()).append(" ")
                    .append("-h ").append(info.getHost()).append(" ")
                    .append("-P ").append(info.getPort()).append(" ")
                    .append("--single-transaction ").append(task.getExtraParams()).append(" ")
                    .append(info.getDbName()).append(" ")
                    .append(table);
            //输出命令
            log.info("导出命令" + buffer);

            Process exec = null;
            File file = new File(file2, table + ".sql");
            try (FileWriter writer = new FileWriter(file);) {
                exec = Runtime.getRuntime().exec(buffer.toString());
                // 处理标准输出流
                BufferedReader stdInput = new BufferedReader(new InputStreamReader(exec.getInputStream(), StandardCharsets.UTF_8));
                String line;
                while ((line = stdInput.readLine()) != null) {
                    log.info(line);
                    writer.write(line);
                    writer.write("\n");
                }
                // 处理错误输出流
                BufferedReader stdError = new BufferedReader(new InputStreamReader(exec.getErrorStream(), StandardCharsets.UTF_8));
                while ((line = stdError.readLine()) != null) {
                    log.error(line);
                }
                int i = exec.waitFor();
                if (i == 0) {
                    log.info("导出成功");
                } else {
                    log.info("导出失败");
                }

                tableToFile.setFileName(table + ".sql");
                tableToFile.setTableName(table);
                tableToFile.setFileAbsolutePath(file.getAbsolutePath());
                tableToFile.setDbName(info.getDbName());

                tableToFiles.add(tableToFile);

                testException(false);

            } catch (Exception e) {
                dbRecord.setStatus("fail");
                errMsg.append(e.getMessage()).append("\n");
                e.printStackTrace();
            } finally {
                //等文件流操作关闭后才设置文件大小
                tableToFile.setFileSize(file.length());
                if (exec != null) {
                    try {
                        exec.getInputStream().close();
                        exec.getOutputStream().close();
                        exec.getErrorStream().close();
                        exec.destroy();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        dbRecord.setConnName(task.getConnName());
        if (dbRecord.getStatus() == null) {
            dbRecord.setStatus("success");
        }
        dbRecord.setErrMsg(errMsg.toString());
        dbRecord.setTablesToFile(JSON.toJSONString(tableToFiles));
        dbRecord.setStartTime(DateUtils.dateTime(s));
        dbRecord.setTaskName(task.getName());
        dbRecord.setEndTime(new Date());
        return dbRecord;
    }



}
